package com.tanhua.dubbo.server.api;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import com.tanhua.dubbo.server.enums.CommentType;
import com.tanhua.dubbo.server.enums.IdType;
import com.tanhua.dubbo.server.pojo.*;
import com.tanhua.dubbo.server.service.IdService;
import com.tanhua.dubbo.server.service.TimeLineService;
import com.tanhua.dubbo.server.vo.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.ArrayList;
import java.util.List;

@Service(version = "1.0.0")
@Slf4j
public class MomentsApiImpl implements MomentsApi {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private IdService idService;

    @Autowired
    private TimeLineService timeLineService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private VideoApi videoApi;

    //评论数据存储在Redis中key的前缀
    private static final String COMMENT_REDIS_KEY_PREFIX = "QUANZI_COMMENT_";

    //用户是否点赞的前缀
    private static final String COMMENT_USER_LIEK_REDIS_KEY_PREFIX = "USER_LIKE_";

    //用户是否喜欢的前缀
    private static final String COMMENT_USER_LOVE_REDIS_KEY_PREFIX = "USER_LOVE_";

    /**
     * 获取redis中评论数据的key的方法
     */
    private String getCommentRedisKeyPrefix(String publishId) {
        return COMMENT_REDIS_KEY_PREFIX + publishId;
    }

    /**
     * 获取redis中用户是否点赞的key的方法
     *
     * @param userId
     * @return
     */
    private String getCommentUserLiekRedisKeyPrefix(Long userId) {
        return COMMENT_USER_LIEK_REDIS_KEY_PREFIX + userId;
    }

    /**
     * 获取redis中用户喜欢的key方法
     *
     * @param userId
     * @return
     */
    private String getCommentUserLoveRedisKeyPrefix(Long userId) {
        return COMMENT_USER_LOVE_REDIS_KEY_PREFIX + userId;
    }

    /**
     * 查询好友动态的实现方法
     *
     * @param userId   登录用户的id
     * @param page     分页参数,当前页
     * @param pageSize 页面数
     * @return
     */
    @Override
    //查询好友动态,实际查询的是时间线表
    public PageInfo<Publish> findPublishList(Long userId, Integer page, Integer pageSize) {
        //创建一个pageInfo对象用于封装返回的数据信息
        PageInfo<Publish> pageInfo = new PageInfo<>();
        pageInfo.setPageNum(page);
        pageInfo.setPageSize(pageSize);
        //使用mongoTempalte的查询以及分页功能,获取到时间线表中的好友动态信息,
        //时间线表中并没有存储具体的动态信息只是存储了发布的动态的id,所以我们需要通过发布的动态的id去发布动态表中查询具体的动态信息
        Query query = new Query();
        Pageable pageRequest = PageRequest.of(page - 1, pageSize, Sort.by(Sort.Order.desc("date")));
        query.with(pageRequest);
        List<TimeLine> timeLines = mongoTemplate.find(query, TimeLine.class, "quanzi_time_line_" + userId);
        //使用hutool工具判断集合是否为空
        if (CollUtil.isEmpty(timeLines)) {
            //没有查询到数据
            return pageInfo;
        }
        /*List<ObjectId> ids = new ArrayList<>();
        //获取时间线列表中的发布id的列表
        for (TimeLine timeLine : timeLines) {
            ids.add(timeLine.getPublishId());
        }*/
        List<Object> ids = CollUtil.getFieldValues(timeLines, "publishId");
        //根据发布id查询动态列表
        Query queryPublish = Query.query(Criteria.where("id").in(ids)).with(Sort.by(Sort.Order.desc("created")));
        List<Publish> publishList = mongoTemplate.find(queryPublish, Publish.class);
        //将查询到底额 动态列表封装到pageInfo对象中
        pageInfo.setRecords(publishList);

        return pageInfo;
    }

    /**
     * 发布动态
     *
     * @param publish
     * @return
     */
    @Override
    public String savePublish(Publish publish) {
        //对publish进行校验,如果发送的文本内容和发布用户的ID都为空,说明发布失败
        if (!ObjectUtil.isAllNotEmpty(publish.getText(), publish.getUserId())) {
            return null;
        }
        //设置主键ID
        publish.setId(ObjectId.get());

        try {
            //设置自增长的pid
            publish.setPid(idService.createId(IdType.PUBLISH));
            publish.setCreated(System.currentTimeMillis());

            //写入到publish表中
            mongoTemplate.save(publish);

            //写入相册表
            Album album = new Album();
            album.setPublishId(publish.getId());
            album.setId(ObjectId.get());
            album.setCreated(System.currentTimeMillis());

            mongoTemplate.save(album, "quanzi_album_" + publish.getUserId());

            //写入好友的时间线表(使用异步写入)
            timeLineService.saveTimeLine(publish.getUserId(), publish.getId());
        } catch (Exception e) {
            //TODO 需要做事务的回滚，Mongodb的单节点服务，不支持事务，对于回滚我们暂时不实现了
            log.error("发布动态失败~ publish = " + publish, e);
        }
        return publish.getId().toHexString();
    }

    /**
     * 查看推荐动态
     *
     * @param userId   登录用户的ID
     * @param page     当前页
     * @param pageSize 每页显示条目数
     * @return
     */
    @Override
    public PageInfo<Publish> findRecommendPublishList(Long userId, Integer page, Integer pageSize) {
        PageInfo<Publish> pageInfo = new PageInfo<>();
        pageInfo.setPageNum(page);
        pageInfo.setPageSize(pageSize);
        //查看redis中存储的推荐用户的pid
        String recommendPids = redisTemplate.opsForValue().get("QUANZI_PUBLISH_RECOMMEND_" + userId);
        if (StringUtils.isEmpty(recommendPids)) {
            return pageInfo;
        }
        String[] pids = recommendPids.split(",");
        List<Long> pidsList = new ArrayList<>();
        for (String pid : pids) {
            Long value = Long.valueOf(pid);
            pidsList.add(value);
        }

        Pageable pageRequest = PageRequest.of(page - 1, pageSize, Sort.by(Sort.Order.desc("created")));
        Query query = Query.query(Criteria.where("pid").in(pidsList)).with(pageRequest);
        List<Publish> publishList = mongoTemplate.find(query, Publish.class);

        if (CollUtil.isEmpty(publishList)) {
            return pageInfo;
        }
        pageInfo.setRecords(publishList);
        return pageInfo;
    }

    /**
     * 通过动态ID查询动态的方法
     *
     * @param publishId 动态的ID
     * @return
     */
    @Override
    public Publish findPublishById(String publishId) {
        return mongoTemplate.findById(new ObjectId(publishId), Publish.class);
    }

    /**
     * 查询点赞数量的方法
     *
     * @param publishId
     * @return
     */
    @Override
    public Long findLikeCount(String publishId) {
        //从redis中命中查询,如果命中直接返回即可
        String redisKey = getCommentRedisKeyPrefix(publishId);
        String hashKey = CommentType.LIKE.toString();
        //从redis中获取点赞数
        Object data = redisTemplate.opsForHash().get(redisKey, hashKey);
        if (ObjectUtil.isNotEmpty(data)) {
            //使用hutool工具将data转为long类型
            return Convert.toLong(data);
        }
        //如果redis中没有数据,再去查询mongodb中的数据,以免由于redis中数据丢失导致的数据查询失败的问题
        Long count = findCommentCount(publishId, CommentType.LIKE);
        //查询到之后再写入redis中
        redisTemplate.opsForHash().put(redisKey, hashKey, StrUtil.toString(count));
        return count;
    }

    /**
     * 点赞方法
     *
     * @param
     * @param publishId
     * @return
     */
    @Override
    public Boolean likeComment(Long userId, String publishId) {
        //先判断用户是否已经点赞,如果已经点赞了就直接返回
        Boolean result = findLikeRecord(userId, publishId);
        if (result) {
            //已经点过赞了
            return false;
        }
        //没有点赞过则将点赞数据保存至评论表中
        Boolean saveComment = saveComment(userId, publishId, CommentType.LIKE, null);
        if (!saveComment) {
            //保存失败返回false
            return false;
        }
        //修改redis中的点赞数以及是否点赞
        String redisKey = getCommentRedisKeyPrefix(publishId);
        String hashKey = CommentType.LIKE.toString();
        //将点赞数+1
        redisTemplate.opsForHash().increment(redisKey, hashKey, 1);
        //将是否点赞的状态修改为1
        String userHashKey = getCommentUserLiekRedisKeyPrefix(userId);
        redisTemplate.opsForHash().put(redisKey, userHashKey, "1");

        return true;
    }

    /**
     * 保存评论的方法
     *
     * @param userId      登录用户的ID
     * @param publishId   动态ID/主键ID ,此参数是动态的,依据具体方法执行是时传递的参数为准
     * @param commentType 评论类型
     * @param content     评论的内容
     * @return
     */
    private Boolean saveComment(Long userId, String publishId, CommentType commentType, String content) {
        try {
            Comment comment = new Comment();
            comment.setId(ObjectId.get());//这条评论的主键ID
            comment.setPublishId(new ObjectId(publishId));//动态的ID
            comment.setCommentType(commentType.getType());//动态的类型
            comment.setContent(content);//评论的内容
            comment.setUserId(userId);//发表评论的用户的ID
            comment.setCreated(System.currentTimeMillis());//创建时间
            Publish publish = findPublishById(publishId);//通过主键ID去动态表查找这条动态
            if (ObjectUtil.isNotEmpty(publish)) {//如果不为空,则说明查找成功
                //查找这条动态发布者的ID,将其赋值给PublishUserId
                comment.setPublishUserId(publish.getUserId());//publishUserId是发布动态的用户id
                //如果为空,在考虑其他类型,如:评论
            } else {
                //如果上一步判断为空,则也可能表示目前的评论对象不是动态,而是评论或其他类型
                Comment myComment = findCommentById(publishId);//当调用评论的方法的时候传递过来的publishId实际是主键ID去评论表中查找这条评论
                if (ObjectUtil.isNotEmpty(myComment)) {//如果不为空代表查找成功
                    //myComment.getUserId()获取的是评论人的ID
                    comment.setPublishUserId(myComment.getUserId());
                } else {
                    Video video = videoApi.findVideoById(publishId);
                    if (ObjectUtil.isNotEmpty(video)){
                        comment.setPublishUserId(video.getUserId());
                    }
                }
            }
            mongoTemplate.save(comment);
            return true;
        } catch (Exception e) {
            log.error("保存Comment出错~ userId = " + userId + ", publishId = " + publishId + ", commentType = " + commentType, e);
        }
        return false;
    }

    /**
     * 通过ID查找评论
     *
     * @param publishId 动态ID
     * @return
     */
    private Comment findCommentById(String publishId) {
        return mongoTemplate.findById(new ObjectId(publishId), Comment.class);
    }

    /**
     * 查看该用户是否点赞过的方法
     *
     * @param userId
     * @param publishId
     * @return
     */
    public Boolean findLikeRecord(Long userId, String publishId) {
        //从redis中查数据
        String redisKey = getCommentRedisKeyPrefix(publishId);
        String userHashKey = getCommentUserLiekRedisKeyPrefix(userId);
        Object data = redisTemplate.opsForHash().get(redisKey, userHashKey);
        //判断数据是否为空,不为空则判断
        if (ObjectUtil.isNotEmpty(data)) {
            //使用hutool工具比较数据与"1"是否一致,返回结果
            return Convert.toStr(data).equals("1");
        }
        //如果为空,则需要去mongodb中再查询,以防redis中因为数据丢失导致查询不到数据
        Query query = Query.query(Criteria.where("publishId").is(new ObjectId(publishId))
                .and("userId").is(userId).and("commentType").is(CommentType.LIKE.getType()));
        long count = mongoTemplate.count(query, Comment.class);
        //如果查询结果为0,说明用户对于该动态没有进行过点赞
        if (count == 0) {
            return false;
        }
        //将数据写入到redis中
        redisTemplate.opsForHash().put(redisKey, userHashKey, "1");
        return true;
    }

    /**
     * 取消点赞的方法
     *
     * @param userId
     * @param publishId
     * @return
     */
    @Override
    public Boolean disLikeComment(Long userId, String publishId) {
        //先判断用户是否已经点赞,如果没有点赞就返回
        Boolean likeRecord = findLikeRecord(userId, publishId);
        if (!likeRecord) {
            return false;
        }
        //如果点赞过将数据从评论表中删除
        Boolean result = deletComment(userId, publishId, CommentType.LIKE);
        if (!result) {
            return false;
        }
        //修改redis中的数据
        String redisKey = getCommentRedisKeyPrefix(publishId);
        String hashKey = CommentType.LIKE.toString();
        //修改点赞数-1
        redisTemplate.opsForHash().increment(redisKey, hashKey, -1);
        String userHashKey = getCommentUserLiekRedisKeyPrefix(userId);
        //用户是否点赞状态删除
        redisTemplate.opsForHash().delete(redisKey, userHashKey);
        return true;
    }

    /**
     * 对推荐动态喜欢的方法
     * @param userId
     * @param publishId
     * @return
     */
    @Override
    public Boolean loveComment(Long userId, String publishId) {
        //先判断用户是否已经喜欢,如果已经喜欢了就返回
        Boolean result = findLoveRecord(userId, publishId);
        if (result) {
            //已经点过赞了
            return false;
        }
        //没有喜欢就将数据存入redis和数据库中
        Boolean saveResult = saveComment(userId, publishId, CommentType.LOVE, null);
        if (!saveResult) {
            return false;
        }
        String redisKey = getCommentRedisKeyPrefix(publishId);
        String hashKey = CommentType.LOVE.toString();
        //喜欢数加一
        redisTemplate.opsForHash().increment(redisKey, hashKey, 1);
        //喜欢状态设置为1
        String userHashKey = getCommentUserLoveRedisKeyPrefix(userId);
        redisTemplate.opsForHash().put(redisKey, userHashKey, "1");
        return true;
    }

    /**
     * 查询喜欢记录,是否喜欢过
     *
     * @param userId
     * @param publishId
     * @return
     */
    public Boolean findLoveRecord(Long userId, String publishId) {
        //从redis中查询
        String redisKey = getCommentRedisKeyPrefix(publishId);
        String userHashKey = getCommentUserLoveRedisKeyPrefix(userId);
        Object data = redisTemplate.opsForHash().get(redisKey, userHashKey);
        if (ObjectUtil.isNotEmpty(data)) {
            //看结果是否为1,是则返回true
            return StrUtil.equals(Convert.toStr(data), "1");
        }
        //如果redis中没有,还得再去monggodb中查询确认
        Query query = Query.query(Criteria.where("publishId").is(new ObjectId(publishId))
                .and("userId").is(userId).and("commentType").is(CommentType.LOVE.getType()));
        long count = mongoTemplate.count(query, Comment.class);
        if (count == 0) {
            return false;
        }
        redisTemplate.opsForHash().put(redisKey, userHashKey, "1");
        return true;
    }

    /**
     * 查询评论列表
     *
     * @param publishId
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public PageInfo<Comment> findCommentList(String publishId, Integer page, Integer pageSize) {
        //
        Pageable pageRequest = PageRequest.of(page - 1, pageSize, Sort.by(Sort.Order.asc("created")));
        Query query = Query.query(Criteria.where("publishId").is(new ObjectId(publishId)).
                and("commentType").is(CommentType.COMMENT.getType())).with(pageRequest);
        //查询到的评论列表
        List<Comment> commentList = mongoTemplate.find(query, Comment.class);
        PageInfo<Comment> pageInfo = new PageInfo<>();
        pageInfo.setPageSize(pageSize);
        pageInfo.setPageNum(page);
        pageInfo.setRecords(commentList);
        return pageInfo;
    }

    /**
     * 发表评论
     *
     * @param userId
     * @param publishId
     * @param content
     * @return
     */
    @Override
    public Boolean sendComment(Long userId, String publishId, String content) {
        //调用已编写好的保存评论的方法,将评论数据保存到数据库中
        return saveComment(userId, publishId, CommentType.COMMENT, content);
    }

    /**
     * 查询喜欢数的方法
     *
     * @param publishId
     * @return
     */
    @Override
    public Long findLoveCount(String publishId) {
        //首先从redis中命中
        String redisKey = getCommentRedisKeyPrefix(publishId);
        String hashKey = CommentType.LOVE.toString();

        Object data = redisTemplate.opsForHash().get(redisKey, hashKey);
        //如果不为空,将数量返回
        if (ObjectUtil.isNotEmpty(data)) {
            return Convert.toLong(data);
        }
        //
        Long count = findCommentCount(publishId, CommentType.LOVE);
        redisTemplate.opsForHash().put(redisKey, hashKey, StrUtil.toString(count));
        return count;
    }

    /**
     * 取消喜欢
     *
     * @param userId
     * @param publishId
     * @return
     */
    @Override
    public Boolean unLoveComment(Long userId, String publishId) {
        Boolean loveRecord = findLoveRecord(userId, publishId);
        if (!loveRecord) {
            return false;
        }
        Boolean comment = deletComment(userId, publishId, CommentType.LOVE);
        if (!comment) {
            return false;
        }
        String redisKey = getCommentRedisKeyPrefix(publishId);
        String hashKey = CommentType.LOVE.toString();
        redisTemplate.opsForHash().increment(redisKey, hashKey, -1);

        String userHashKey = getCommentUserLoveRedisKeyPrefix(userId);
        redisTemplate.opsForHash().delete(redisKey, userHashKey);

        return true;
    }

    /**
     * 删除评论的方法
     *
     * @param userId
     * @param publishId
     * @param commentType
     * @return
     */
    private Boolean deletComment(Long userId, String publishId, CommentType commentType) {
        Query query = Query.query(Criteria.where("userId").is(userId)
                .and("publishId").is(new ObjectId(publishId)).and("commentType").is(commentType.getType()));
        return mongoTemplate.remove(query, Comment.class).getDeletedCount() > 0;
    }

    /**
     * 查询评论数量的方法
     *
     * @param publishId
     * @param commentType
     * @return
     */
    private Long findCommentCount(String publishId, CommentType commentType) {
        //mongodb中通过quanzi_comment表中查询
        Query query = Query.query(Criteria.where("publishId").is(new ObjectId(publishId))
                .and("commentType").is(commentType.getType()));
        return mongoTemplate.count(query, Comment.class);
    }

    /**
     * 查看视频评论的数量
     * @param videoId
     * @return
     */
    @Override
    public Long findCommentCount(String videoId) {
        return findCommentCount(videoId,CommentType.COMMENT);
    }

    /**
     * 消息模块查看所有点赞列表
     * @param id
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public PageInfo<Comment> findLikeList(Long id, Integer page, Integer pageSize) {
        return findAllCommentList(id,CommentType.LIKE,page,pageSize);
    }

    /**
     * 消息模块查看所有喜欢列表
     * @param id
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public PageInfo<Comment> findLoveList(Long id, Integer page, Integer pageSize) {
        return findAllCommentList(id,CommentType.LOVE,page,pageSize);
    }

    /**
     * 消息模块查看所有评论列表
     * @param userId
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public PageInfo<Comment> findCommentList(Long userId, Integer page, Integer pageSize) {
        return findAllCommentList(userId,CommentType.COMMENT,page,pageSize);
    }

    /**
     * 查看用户的动态信息
     * @param userId
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public PageInfo<Publish> findOneUserPublish(Long userId, Integer page, Integer pageSize) {
        PageInfo<Publish> pageInfo = new PageInfo<>();
        pageInfo.setPageNum(page);
        pageInfo.setPageSize(pageSize);
        Pageable pageRequest = PageRequest.of(page,pageSize,Sort.by(Sort.Order.desc("created")));
        Query query = Query.query(Criteria.where("userId").is(userId)).with(pageRequest);
        List<Publish> publishList = mongoTemplate.find(query, Publish.class);
        if(CollUtil.isEmpty(publishList)){
            return pageInfo;
        }
        pageInfo.setRecords(publishList);
        return pageInfo;
    }

    /**
     * 公用的查看评论列表的方法,根据发布者的ID和评论类型进行查找
     * @param userId 发布评论者的ID
     * @param commentType 评论类型
     * @param page 当前页
     * @param pageSize 页数
     * @return
     */
    private PageInfo<Comment> findAllCommentList(Long userId,CommentType commentType,Integer page,Integer pageSize){
        Pageable pageRequest = PageRequest.of(page-1,pageSize,Sort.by(Sort.Order.desc("created")));
        Query query = Query.query(Criteria.where("publishUserId").is(userId).and("commentType").is(commentType.getType()))
                .with(pageRequest);
        List<Comment> commentList = mongoTemplate.find(query, Comment.class);
        PageInfo<Comment> pageInfo = new PageInfo<>();
        pageInfo.setPageNum(page);
        pageInfo.setPageSize(pageSize);
        pageInfo.setRecords(commentList);
        return pageInfo;
    }
}
